''' <summary>
''' The main WRDB class used by the stand-alone client and MapWindow interface; its purpose is to open the last project and then display the appropriate main form
''' </summary>
''' <remarks>
''' The usage of WRDBInterface.clsWRDB facilitates usage of this DLL by the MapWindow/BASINS plug-in. The plug-in class references the interface DLL, and
''' at run-time, this real DLL is dynamically loaded from the "program files" directory location. This dll places the full path of itself in a registry
''' setting that the plug-in looks for.
''' </remarks>
Public Class clsWRDB
    Implements WRDB.Interfaces.clsWRDB

    Public Event Closed() Implements Interfaces.clsWRDB.Closed

    Friend WithEvents Updates As New clsUpdates("WRDB", "www.wrdb.com")

    Private ShutdownCommandline As String = ""

    Public Enum enumReportScaling
        None
        Stretched
        Multiline
        ScaledFonts
    End Enum

    ''' <summary>
    ''' Options corresponding to items in options combobox in Select forms
    ''' </summary>
    Public Enum enumSelectOption
        SelectActiveCreate
        SelectAllCreate
        SelectActiveAppend
        SelectDrilldown
    End Enum

    Public Enum enumReportLanguage
        English
        Spanish
    End Enum

    'Private _ProxyAddress, _ProxyUsername, _ProxyPassword As String
    Private _SkipTempDelete As Boolean

    Friend SuspendMapUpdate As Boolean = False
    Private _MapWin As MapWindow.Interfaces.IMapWin
    Private _GISMap As AxMapWinGIS.AxMap
    Friend WRDBGroupName As String = "Water Resources Database"
    Friend WRDBLayerName As String = "WRDB Stations"
    Friend WRDBShapeDirectory As String
    Public WRDBStationTypes As New Generic.List(Of String) 'prior list of station types (so won't have to requery data maybe)


#Region "Properties"

    Public Property ExpertMode As Boolean

    Public Property ReportHeading1 As String

    Public Property ReportHeading2 As String

    Public Property ReportScaling As enumReportScaling

    Public Property ReportLanguage As enumReportLanguage

    Public Property ShowStartup As Boolean

    Public Property ShowGettingStarted As Boolean

    Public Property ShowNewProject As Boolean

    Public Property CheckUpdates As Boolean

    Public Property UpdateLocation As String

    Public Property GridFont As Font

    Public Property PromptImport As Boolean

    Public Property UserName As String

    'Public Property ProxyAddress() As String
    '    Get
    '        Return _ProxyAddress
    '    End Get
    '    Set(ByVal value As String)
    '        _ProxyAddress = value
    '    End Set
    'End Property

    'Public Property ProxyUserName() As String
    '    Get
    '        Return _ProxyUsername
    '    End Get
    '    Set(ByVal value As String)
    '        _ProxyUsername = value
    '    End Set
    'End Property

    'Public Property ProxyPassword() As String
    '    Get
    '        Return _ProxyPassword
    '    End Get
    '    Set(ByVal value As String)
    '        _ProxyPassword = value
    '    End Set
    'End Property

#End Region

    ''' <summary>
    ''' Opens the last project used by WRDB
    ''' </summary>
    Public Sub New()
        Try
            If Process.GetProcessesByName(Process.GetCurrentProcess.ProcessName).Length > 1 AndAlso Not Debugger.IsAttached Then
                WarningMsg("You have opened multiple copies of WRDB; please be aware that WRDB is designed as a single-instance application. Data selected in one copy of WRDB will overwrite those from other copies of WRDB if they are accessing the same shared database which can cause unexpected results. Furthermore, most global settings are shared among all running instances of the program.\n\nThe new instance of WRDB will be started, but please use care to avoid accessing the same database from multiple instances.")
            End If

            DLLPath = IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)
            SaveSetting(REGAPP, "Defaults", "DLL Path", DLLPath)

            AppDataPath = GetSetting(REGAPP, "General", "AppDataPath", AppDataPath)

            If Not My.Computer.FileSystem.DirectoryExists(AppDataPath) Then My.Computer.FileSystem.CreateDirectory(AppDataPath)
            If Not My.Computer.FileSystem.DirectoryExists(clsProject.TempPath) Then My.Computer.FileSystem.CreateDirectory(clsProject.TempPath)

            MainWRDB = Me
            WRDBForms = New Collection

            IniFile = New clsWRDBIniFile()
            With IniFile
                If .Exists Then
                    ShowStartup = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowStartup, "True")
                    ShowNewProject = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowNewProject, "True")
                    ShowGettingStarted = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowGettingStarted, "True")
                    CheckUpdates = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppCheckUpdates, "True")
                    UpdateLocation = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppUpdateLocation, "http://www.wrdb.com/download/wrdb5/")
                    'ProxyAddress = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyAddress, "")
                    'ProxyUserName = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyUsername, "")
                    'ProxyPassword = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyPassword, "")
                    ExpertMode = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppExpertMode, "False")
                    ReportHeading1 = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptHeading1, "")
                    ReportHeading2 = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptHeading2, "")
                    ReportScaling = Val(.GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptScaling, CInt(enumReportScaling.ScaledFonts).ToString))
                    ReportLanguage = Val(.GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptLanguage, CInt(enumReportLanguage.English).ToString))
                    PromptImport = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPromptImport, "False")
                    UserName = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppUserName, GetUserName)
                    PrintDoc.DefaultPageSettings.Margins.Left = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsLeft, "100")
                    PrintDoc.DefaultPageSettings.Margins.Right = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsRight, "100")
                    PrintDoc.DefaultPageSettings.Margins.Top = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsTop, "100")
                    PrintDoc.DefaultPageSettings.Margins.Bottom = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsBottom, "100")
                    GridFont = New Font(.GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppGridFontName, "Tahoma"), Val(.GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppGridFontSize, "8.25")))

                    'Dim LastProjectName As String = .GetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppLastProjectName)
                    'If LastProjectName <> "" AndAlso .GetProjectNames().Contains(LastProjectName) Then
                    '    Project = Nothing
                    '    If Not DataProviderAvailable(LastProjectName) Then Exit Sub
                    '    Try
                    '        Project = New clsProject(LastProjectName)
                    '    Catch ex As DbSqlException
                    '        WarningMsg(ex.Message)
                    '        Exit Sub
                    '    End Try
                    'End If
                End If
            End With

        Catch ex As IO.IOException
            WarningMsg(IniFile.ErrorText(ex))
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            'If IniFile IsNot Nothing AndAlso IniFile.Exists Then IniFile.SaveAndClose()
        End Try
    End Sub

    Public Function GetMainFormMW() As Form Implements Interfaces.clsWRDB.GetMainFormMW
        IsMWForm = True
        MainFormMW = New frmMainMW
        MainFormMW.Visible = False
        MainForm = MainFormMW
        Return MainFormMW
    End Function

    Private SplashForm As New frmSplash
    Private WithEvents bwSplash As System.ComponentModel.BackgroundWorker

    Public Sub ShowSplashScreen()
        'Exit Sub
        bwSplash = New System.ComponentModel.BackgroundWorker
        bwSplash.WorkerSupportsCancellation = True
        bwSplash.RunWorkerAsync()
    End Sub

    Public Sub CloseSplashScreen()
        If bwSplash IsNot Nothing Then bwSplash.CancelAsync()
        Application.DoEvents()
    End Sub

    Private Sub bwSplash_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bwSplash.DoWork
        'show the splash screen
        SplashForm.Show()
        Dim t1 As Single = My.Computer.Clock.TickCount
        Application.DoEvents()
        Dim i As Integer = 0
        While Not bwSplash.CancellationPending
            Threading.Thread.Sleep(100)
            i += 1
        End While
        SplashForm.Close()
        SplashForm.Dispose()
    End Sub

    Private Sub bwSplash_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bwSplash.RunWorkerCompleted
        bwSplash.Dispose()
    End Sub

    ''' <summary>
    ''' Display the main form used by the stand-alone WRDB client
    ''' </summary>
    Public Sub ShowMainForm()
        Try
            IsMWForm = False
            MainForm = New frmMain
            With MainForm
                .StatusBarPanelText.Text = CurrentProjectName
                'If Project IsNot Nothing Then SetProject(Project.Name)
                .ShowDialog()
                'need to run installer as late as possible to make sure WRDB is fully shut down
                'UpdatesInstalled sets ShutdownCommandline to non-empty string, and that gets executed here
                If ShutdownCommandline <> "" Then Process.Start(ShutdownCommandline)
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Display the main form used by the stand-alone WRDB client
    ''' </summary>
    Public Sub ShowMainFormView()
        IsViewForm = True
        MainForm = New frmMainView
        With MainForm
            .ShowDialog()
        End With
    End Sub

    ''' <summary>
    ''' Display the main form used by the MapWindow plug-in
    ''' </summary>
    ''' <remarks>This form is reformatted to be smaller, and included GIS-interface items</remarks>
    Public Sub ShowMainFormMW() Implements Interfaces.clsWRDB.ShowMainFormMW
        With CType(MainForm, frmMainMW)
            .Visible = False
            Application.DoEvents()
            .StartPosition = FormStartPosition.CenterScreen
            .ShowInTaskbar = False
            .SendToBack()
            .SuspendLayout()
            'If Not String.IsNullOrEmpty(CurrentProjectName) Then SetProject(CurrentProjectName)
            '.StatusBarPanelText.Text = CurrentProjectName
            '.Text = "WRDB - " & CurrentProjectName
            .WindowState = FormWindowState.Minimized
            .Show()
            .FirstTimeCheck()
            .Mode = frmMainMW.enumMode.Display
            If .chkRefresh.Checked Then
                .CreateLayer()
                .RefreshDisplay()
            End If
            .Visible = False
        End With
    End Sub

    Public Sub CloseMainFormMW() Implements Interfaces.clsWRDB.CloseMainFormMW
        If MainForm IsNot Nothing Then
            MainForm.Close()
            'MainForm.Dispose()
        End If
        MainFormMW = Nothing
        MainForm = Nothing
    End Sub

    ''' <summary>
    ''' Client form displayed from MapWindow for testing
    ''' </summary>
    Public Sub ShowMainFormFullMW()
        MainForm = New frmMain
        With MainForm
            GetWindowPos(REGAPP, MainForm)
            .StatusBarPanelText.Text = CurrentProjectName
            .Text = "WRDB - " & CurrentProjectName
            .Show()
        End With
    End Sub

    'Public Sub ShowGISForm(ByVal mode As WRDB.Graph.frmGIS.enumMode)
    '    With New WRDB.Graph.frmGIS(Project, mode)
    '        .ShowDialog()
    '    End With
    'End Sub

#Region "GIS Routines"

    Friend Sub MapMouseMove(ByVal ScreenX As Integer, ByVal ScreenY As Integer) Implements Interfaces.clsWRDB.MapMouseMove
        If MainForm IsNot Nothing Then CType(MainForm, frmMainMW).MapMouseMove(ScreenX, ScreenY)
    End Sub

    Friend Sub MapExtentsChanged() Implements Interfaces.clsWRDB.MapExtentsChanged
        If MainForm IsNot Nothing Then CType(MainForm, frmMainMW).MapExtentsChanged()
    End Sub

    Friend Sub ShapesSelected() Implements Interfaces.clsWRDB.ShapesSelected
        If MainForm IsNot Nothing Then CType(MainForm, frmMainMW).MapShapesSelected()
    End Sub

    'Public Property SelectedStations() As Generic.List(Of String) 'Implements WRDB.Interfaces.clsWRDB.SelectedStations
    '    Get
    '        Return modWRDB.SelectedStations
    '    End Get
    '    Set(ByVal value As Generic.List(Of String))
    '        modWRDB.SelectedStations = value
    '    End Set
    'End Property

    'Public WriteOnly Property StationHover() As String 'Implements WRDB.Interfaces.clsWRDB.StationHover
    '    Set(ByVal value As String)
    '        CType(MainForm, frmMainMW).StationHover(value)
    '    End Set
    'End Property

    'Public Property GISProjectPath() As String 'Implements WRDB.Interfaces.clsWRDB.GISProjectPath
    '    Get
    '        Return modWRDB.GISProjectPath
    '    End Get
    '    Set(ByVal value As String)
    '        modWRDB.GISProjectPath = value
    '    End Set
    'End Property

    ''' <summary>
    ''' Mapwindows application object; coerce to MapWindow.Interfaces.IMapWin
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public WriteOnly Property IMapWin() As Object Implements Interfaces.clsWRDB.MapWin
        Set(ByVal value As Object)
            _MapWin = value
            _GISMap = _MapWin.GetOCX
            If String.IsNullOrEmpty(_MapWin.Project.FileName) Then
                WRDBShapeDirectory = AppDataPath 'my documents\WRDB
            Else
                WRDBShapeDirectory = IO.Path.GetDirectoryName(_MapWin.Project.FileName) & "\WRDB"
            End If
            If Not My.Computer.FileSystem.DirectoryExists(WRDBShapeDirectory) Then My.Computer.FileSystem.CreateDirectory(WRDBShapeDirectory)
            Graph.ProjectProjection = _MapWin.Project.ProjectProjection
        End Set
    End Property

    ''' <summary>
    ''' Mapwindows application object; coerce to MapWindow.Interfaces.IMapWin
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <CLSCompliant(False)> _
    Public ReadOnly Property MapWin() As MapWindow.Interfaces.IMapWin
        Get
            Return _MapWin
        End Get
    End Property

    Friend ReadOnly Property GISMap() As AxMapWinGIS.AxMap
        Get
            Return _GISMap
        End Get
    End Property

#End Region

#Region "Events Monitored by GIS application"

    Friend Sub RaiseFormClosed()
        RaiseEvent Closed()
    End Sub

#End Region

    Friend Sub CloseWRDB()
        Try
            If IniFile IsNot Nothing AndAlso IniFile.Exists Then
                With IniFile
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowStartup, ShowStartup.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowNewProject, ShowNewProject.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppShowGettingStarted, ShowGettingStarted.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppCheckUpdates, CheckUpdates.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppUpdateLocation, UpdateLocation)
                    '.SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyAddress, ProxyAddress)
                    '.SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyUsername, ProxyUserName)
                    '.SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppProxyPassword, ProxyPassword)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppExpertMode, ExpertMode.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptHeading1, ReportHeading1)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptHeading2, ReportHeading2)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptScaling, CInt(ReportScaling).ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppRptLanguage, CInt(ReportLanguage).ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPromptImport, PromptImport.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppUserName, UserName)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsLeft, PrintDoc.DefaultPageSettings.Margins.Left.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsRight, PrintDoc.DefaultPageSettings.Margins.Right.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsTop, PrintDoc.DefaultPageSettings.Margins.Top.ToString)
                    .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppPrintMarginsBottom, PrintDoc.DefaultPageSettings.Margins.Bottom.ToString)
                    If _GridFont IsNot Nothing Then
                        .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppGridFontName, _GridFont.Name)
                        .SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppGridFontSize, _GridFont.SizeInPoints.ToString)
                    End If
                End With
            End If
            'IniFile = Nothing

            'if this is the last instance running, clear out the temp directory
            If Process.GetProcessesByName(Process.GetCurrentProcess.ProcessName).Length = 1 And Not _SkipTempDelete Then
                Try
                    If My.Computer.FileSystem.DirectoryExists(clsProject.TempPath) Then My.Computer.FileSystem.DeleteDirectory(clsProject.TempPath, FileIO.DeleteDirectoryOption.DeleteAllContents)
                Catch ex As Exception
                End Try
            End If

            If Project Is Nothing OrElse Project.DB Is Nothing OrElse Project.DB.Connection Is Nothing Then Exit Sub

            Project.CleanDatabase()

        Catch ex As IO.IOException
            WarningMsg(IniFile.ErrorText(ex))
        Catch ex As DbSqlException
            WarningMsg(ex.Message)
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            IniFile.Save()
        End Try
    End Sub

    Friend Function UpdatesInstalled(ByVal ShowIsUpdatedMsg As Boolean) As Boolean
        If Updates.UpdatesInstalled(UpdateLocation, ShowIsUpdatedMsg, ShutdownCommandline) Then
            MainForm.Close()
            Return True
        Else
            Return False
        End If
    End Function

    ''' <summary>
    ''' See if data provider library is available for specified project; if not and user cancelled or was not installed, return false
    ''' </summary>
    ''' <param name="ProjectName">Name of project</param>
    Public Function DataProviderAvailable(ByVal ProjectName As String) As Boolean
        Dim IniFile As New clsWRDBIniFile
        Try
            Dim dbType As clsDB.enumDBType = System.Enum.Parse(GetType(clsDB.enumDBType), IniFile.GetKey(ProjectName, clsWRDBIniFile.enumKey.DatabaseType, clsDB.enumDBType.SQLite.ToString))
            Return DataProviderAvailable(dbType)
        Catch ex As IO.IOException
            WarningMsg(IniFile.ErrorText(ex))
        Finally
            IniFile = Nothing
        End Try
    End Function

    ''' <summary>
    ''' See if data provider library is available for specified type; if not and user cancelled or was not installed, return false
    ''' </summary>
    ''' <param name="dbType">Data provider type</param>
    <CLSCompliant(False)> _
    Public Function DataProviderAvailable(ByVal dbType As clsDB.enumDBType) As Boolean
        If clsDB.EnumDataProviders().Contains(dbType) Then
            Return True
        Else
            CloseSplashScreen()
            WarningMsgFmt("The selected data provider ({0}) is supported by WRDB but the necessary libraries for that provider were not found; please reinstall WRDB.", dbType.ToString)
            Return False
        End If
    End Function

    Private Sub Updates_Progress(ByVal Value As Integer, ByVal MaxValue As Integer, ByVal Text As String) Handles Updates.Progress
        MainForm.WriteProgress(Value, MaxValue, Text, ProgressBarStyle.Blocks, False)
    End Sub

    Private Sub Updates_ProgressComplete() Handles Updates.ProgressComplete
        MainForm.ProgressComplete()
    End Sub

End Class

''' <summary>
''' Class to accept a sequence of values and times and filter then (one at a time) based on threshold criteria
''' If current value exceeds criteria, prior filtered point is returned so it can be saved
''' </summary>
''' <remarks></remarks>
Friend Class clsFilter
    Private Class clsPoint
        Dim _DateTime As Date
        Dim _Value As Single
        Sub New(ByVal DateTime As Date, ByVal Value As Single)
            _DateTime = DateTime
            _Value = Value
        End Sub
        ReadOnly Property DateTime() As Date
            Get
                Return _DateTime
            End Get
        End Property
        ReadOnly Property Value() As Single
            Get
                Return _Value
            End Get
        End Property
    End Class

    Private _dValue As Single 'Minimum value change threshold as percentage of running average
    Private _dSlope As Single 'Minimum slope change threshold as percentage of running average
    Private _Deviation As Single 'Minimum deviation as percentage of running average
    Private _dTMin As Single  'Minimum date-time change threshold (hr)
    Private _dTMax As Single  'Maximum date-time change threshold (hr)
    Private _NumValues As Integer 'number of prior points filtered
    Private _SumValues As Single 'sum of prior points (for running avg)
    Private _LastValue As Single, _LastDateTime As Date 'pending point that will be return when exceeds threshold
    Private _LastFilterValue As Single, _LastFilterDateTime As Date 'last point that was returned true
    Private _LastSlope As Single 'current and prior slope (delta value per hour)

    Private _lstPoints As New Generic.List(Of clsPoint) 'contains list of all points considered since last filtered value

    Private Enum enumReason
        LTDelta
        FirstPoint
        GTdTMax
        GTdSlope
    End Enum
    Private Reason As enumReason

    ''' <summary>
    ''' Initialize the filter class with filter criteria
    ''' </summary>
    ''' <param name="dValue">Minimum value change threshold</param>
    ''' <param name="dSlope">Minimum slope change threshold</param>
    ''' <param name="dTMin">Minimum date-time change threshold (hr)</param>
    ''' <param name="dTMax">Maximum date-time change threshold (hr)</param>
    Friend Sub New(ByVal dValue As Single, ByVal dSlope As Single, ByVal dTMin As Single, ByVal dTMax As Single)
        _dValue = dValue
        _dSlope = dSlope
        _dTMin = dTMin
        _dTMax = dTMax
        _NumValues = 0
        _SumValues = 0
    End Sub

    ''' <summary>
    ''' Initialize the filter class with filter criteria (new algorithm)
    ''' </summary>
    ''' <param name="Deviation">Maximum allowable deviation expressed as percent of average</param>
    Friend Sub New(ByVal Deviation As Single)
        _Deviation = Deviation
        _NumValues = 0
        _SumValues = 0
    End Sub

    ''' <summary>
    ''' Examine next point and determine if it should be added to the filtered point set (returns true); if thresholds not exceeded, returns false
    ''' </summary>
    ''' <param name="Value">Current value</param>
    ''' <param name="DateTime">Current date-time</param>
    Friend Function FilterValue(ByRef DateTime As Date, ByRef Value As Single) As Boolean
        Try
            Dim _Value As Single = Value
            Dim _DateTime As Date = DateTime
            _NumValues += 1
            _SumValues += Value

            If _NumValues = 1 Then 'store first point
                _LastValue = Value
                _LastDateTime = DateTime
                _LastFilterValue = Value
                _LastFilterDateTime = DateTime
                _LastSlope = 0
                Reason = enumReason.FirstPoint
                _lstPoints.Clear()
                _lstPoints.Add(New clsPoint(DateTime, Value))
                Return True
            Else
                Dim dt As Single = DateTime.Subtract(_LastDateTime).TotalHours
                Dim slope As Single = (Value - _LastValue) / (DateTime.Subtract(_LastDateTime).TotalHours)
                'Dim lastslope As Single = (_LastValue - _LastFilterValue) / (_LastDateTime.Subtract(_LastFilterDateTime).TotalHours)
                Dim avg As Single = _SumValues / _NumValues
                Dim ds As Single
                Dim dv As Single
                If avg = 0 Then
                    If slope = _LastSlope Then ds = 0 Else ds = Single.MaxValue
                    If Value = _LastValue Then dv = 0 Else dv = Single.MaxValue
                Else
                    ds = Math.Abs((slope - _LastSlope) / avg) * 100
                    dv = Math.Abs((Value - _LastFilterValue) / avg) * 100
                End If
                Dim maxdev As Single = 0
                If _lstPoints.Count > 1 Then
                    For i As Integer = 1 To _lstPoints.Count - 1
                        With _lstPoints(i)
                            Dim v As Single = InterpolateY(.DateTime, _LastFilterDateTime, _LastFilterValue, DateTime, Value)
                            maxdev = Math.Max(maxdev, Math.Abs(v - .Value))
                        End With
                    Next
                End If
                'add last point if current point causes deviation to be too large
                maxdev = maxdev / avg * 100

                'always add if second point and not too close or dt exceeds max threshold, or dSlope exceeds threshold
                'If (dt > _dTMax OrElse dt > _dTMin OrElse dv > _dValue) AndAlso ds > _dSlope Then
                If (dt > _dTMax OrElse dt > _dTMin OrElse dv > _dValue) AndAlso maxdev > _Deviation Then
                    If dt > _dTMax Then
                        Reason = enumReason.GTdTMax
                    ElseIf ds > _dSlope Then
                        Reason = enumReason.GTdSlope
                    End If
                    'return last value what was pending
                    Value = _LastValue
                    DateTime = _LastDateTime
                    _LastFilterValue = Value
                    _LastFilterDateTime = DateTime
                    _LastValue = _Value
                    _LastDateTime = _DateTime
                    _LastSlope = slope
                    _lstPoints.Clear()
                    _lstPoints.Add(New clsPoint(DateTime, Value))
                    _lstPoints.Add(New clsPoint(_DateTime, _Value))
                    Return True
                Else
                    _LastValue = _Value
                    _LastDateTime = _DateTime
                    _LastSlope = slope
                    Reason = enumReason.LTDelta
                    _lstPoints.Add(New clsPoint(_DateTime, _Value))
                    Return False
                End If
            End If
        Catch ex As Exception
        Finally
            'Debug.Write(" Reason: " & Reason.ToString)
        End Try
    End Function

    ''' <summary>
    ''' Call this to return the last value in the sequence (used for final call)
    ''' </summary>
    ''' <param name="Value">Returns last value</param>
    ''' <param name="DateTime">Returns last  date-time</param>
    Friend Sub LastFilterValue(ByRef DateTime As Date, ByRef Value As Single)
        DateTime = _LastDateTime
        Value = _LastValue
    End Sub
End Class

